<?php namespace Reezy\APICrypt;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamFactoryInterface;
use function base64_decode;
use function json_decode;
use function openssl_private_decrypt;


class APICrypt
{
    /**
     * @var StreamFactoryInterface
     */
    private $streamFactory;
    /**
     * @var string
     */
    protected $privateKey;
    /**
     * @var string
     */
    private $headerName;

    public function __construct(array $config, StreamFactoryInterface $streamFactory)
    {
        $this->privateKey = $config['private_key'];
        $this->headerName = $config['header_name'] ?? 'encryption';
        $this->streamFactory = $streamFactory;
    }

    public function decrypt(ServerRequestInterface $request)
    {

        $encryption = $request->getHeaderLine($this->headerName);

        [$encryptedAesKey, $hexAesIV] = explode(';', $encryption);
        $aesKey = $this->privateDecrypt($encryptedAesKey);
        $aesIv = hex2bin($hexAesIV);
        if (empty($aesKey) || empty($aesIv)) {
            throw new APICryptException('empty aesKey or aesIv ');
        }

        $encrypted = $request->getBody()->getContents();
        if (!empty($encrypted)) {
            $decrypted = json_decode($this->aesDecrypt($encrypted, $aesKey, $aesIv), true);
            if (!empty($decrypted)) {
                return [$request->withParsedBody($decrypted), $aesKey, $aesIv];
            }
        }

        return [$request, $aesKey, $aesIv];
    }

    public function encrypt(ResponseInterface $response, $aesKey, $aesIv)
    {
        $encryptedData = $this->aesEncrypt($response->getBody()->getContents(), $aesKey, $aesIv);

        $body = $this->streamFactory->createStream($encryptedData);

        return $response->withHeader('Content-Type', 'application/octet-stream')->withBody($body);
    }

    private function privateDecrypt(string $encrypted)
    {
        return openssl_private_decrypt(base64_decode($encrypted), $decrypted, $this->privateKey) ? $decrypted : null;
    }

    private function privateEncrypt(string $plain)
    {
        return openssl_private_encrypt($plain, $encrypted, $this->privateKey) ? base64_encode($encrypted) : null;
    }

    private function aesDecrypt($encrypted, $key, $iv)
    {
        return openssl_decrypt($encrypted, "AES-256-CBC", $key, OPENSSL_RAW_DATA, $iv);
    }

    private function aesEncrypt($plain, $key, $iv)
    {
        return openssl_encrypt($plain, "AES-256-CBC", $key, OPENSSL_RAW_DATA, $iv);
    }
}
